import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

import dk.brics.automaton.Automaton;
import dk.brics.automaton.RegExp;

/*
 
 sql:  s0 CREATE s1 TABLE s1 name s0 ( s0 entry*, s0 ) s0
 s0 DROP s1 TABLE s1 name s0
 s0 INSERT s1 INTO s1 name s0 ( s0 name*, s0 ) s0 VALUES s0 ( s0 atom*, s0 ) s0
 s0 SELECT s1 name*, s1 FROM s1 name where? order? s0
 s0 SELECT s0 * s0 FROM s1 name where? s0
 s0 UPDATE s1 name s1 SET s1 update*, where? s0
 s0 DELETE s1 FROM s1 name where? s0
 
 entry: name s1 type s1 property*
 
 type: BIT
 BYTE
 CHAR
 CHAR s0 ( s0 int s0 )
 DATE
 DECIMAL s0 ( s0 INT s0 , s0 INT s0 )
 INT
 INTEGER
 LONG
 LONGWORD
 REAL
 SHORT
 SHORTSTRING
 TIME
 TIMESTAMP
 VARCHAR
 VARCHAR s0 ( s0 int s0 )
 WORD
 
 
 property: NOT s1 NULL
 UNIQUE
 PRIMARY s1 KEY
 
 atom: string | int | name | ?
 
 where: s1 WHERE s1 comp*AND
 
 comp: atom s0 comparison s0 atom
 
 order: s1 ORDER s1 BY s1 name*,
 
 comparison: <, >, <=, >=, <>, =
 
 update: name s0 = s0 atom
 
 */

public class SqlAutomaton {
	static boolean any_in_string = false;
	
	Automaton s0 = Automaton.makeString(" ").repeat();
	Automaton s1 = Automaton.makeString(" ").repeat(1);
	
	Automaton name; 
	Automaton string;
	Automaton intnum;
	Automaton realnum;
	
	Automaton operator(String s) {
		return Automaton.makeString(s);
	}
	
	Automaton keyw(String s) {
		Automaton a = Automaton.makeString(s);
		Automaton b = Automaton.makeString(s.toLowerCase());
		a = a.union(b);
		return a;
	}
	
	Automaton commaplus(Automaton e) {
		return e.concatenate(s0
				.concatenate(operator(","))
				.concatenate(s0)
				.concatenate(e)
				.repeat());
	}
	
	Automaton commastar(Automaton e) {
		return commaplus(e).optional();
	}
	
	Automaton sql() {
		Automaton a = create();
		a = a.union(drop());
		a = a.union(insert());
		a = a.union(select());
		a = a.union(update());
		a = a.union(delete());
		a = s0.concatenate(a).concatenate(s0);
		return a;
	}
	
	Automaton create() {
		return keyw("CREATE").concatenate(s1)
		.concatenate(keyw("TABLE"))
		.concatenate(s1)
		.concatenate(name)
		.concatenate(s0)
		.concatenate(operator("("))
		.concatenate(s0)
		.concatenate(commastar(entry()))
		.concatenate(s0)
		.concatenate(operator(")"));
	}
	
	Automaton drop() { 
		return keyw("DROP").concatenate(s1)
		.concatenate(keyw("TABLE"))
		.concatenate(s1)
		.concatenate(name);
	}
	
	Automaton insert() {
		return keyw("INSERT").concatenate(s1)
		.concatenate(keyw("INTO"))
		.concatenate(s1)
		.concatenate(name)
		.concatenate(s0)
		.concatenate(operator("(")
				.concatenate(s0)
				.concatenate(commastar(name))
				.concatenate(s0)
				.concatenate(operator(")")).optional())
				.concatenate(s0)
				.concatenate(keyw("VALUES"))
				.concatenate(s0)
				.concatenate(operator("("))
				.concatenate(s0)
				.concatenate(commastar(atom()))
				.concatenate(s0)
				.concatenate(operator(")"));    
	}
	
	Automaton select() {
		return keyw("SELECT").concatenate(s1)
		.concatenate(operator("*").union(commastar(name)))
		.concatenate(s1)
		.concatenate(keyw("FROM"))
		.concatenate(commaplus(s1
				.concatenate(name)
				.concatenate(s1
						.concatenate(name).optional()))
						.concatenate(where().optional())
						.concatenate(order().optional()));
	}
	
	Automaton update() {
		return keyw("UPDATE").concatenate(s1)
		.concatenate(name)
		.concatenate(s1)
		.concatenate(keyw("SET"))
		.concatenate(s1)
		.concatenate(commastar(upd()))
		.concatenate(where().optional());
	}
	
	Automaton delete() {
		return keyw("DELETE").concatenate(s1)
		.concatenate(keyw("FROM"))
		.concatenate(s1)
		.concatenate(name)
		.concatenate(where().optional());
	}
	
	Automaton entry() {
		return name.concatenate(s1)
		.concatenate(type())
		.concatenate(property().repeat());
	}
	
	Automaton type() {
		Automaton a = keyw("BIT");
		a = a.union(keyw("BYTE"));
		a = a.union(keyw("CHAR").concatenate(s0
				.concatenate(operator("("))
				.concatenate(s0)
				.concatenate(intnum)
				.concatenate(s0)
				.concatenate(operator(")"))
				.optional()));
		a = a.union(keyw("DATE"));
		a = a.union(keyw("DECIMAL").concatenate(s0)
				.concatenate(operator("(")
						.concatenate(s0)
						.concatenate(intnum)
						.concatenate(s0)
						.concatenate(keyw(","))
						.concatenate(s0)
						.concatenate(intnum)
						.concatenate(s0)
						.concatenate(operator(")"))
						.optional()));
		a = a.union(keyw("INT"));
		a = a.union(keyw("INTEGER"));
		a = a.union(keyw("LONG"));
		a = a.union(keyw("LONGWORD"));
		a = a.union(keyw("REAL"));
		a = a.union(keyw("SHORT"));
		a = a.union(keyw("SHORTSTRING"));
		a = a.union(keyw("TIME"));
		a = a.union(keyw("TIMESTAMP"));
		a = a.union(keyw("VARCHAR").concatenate(s0)
				.concatenate(operator("(")
						.concatenate(s0)
						.concatenate(intnum)
						.concatenate(s0)
						.concatenate(operator(")"))
						.optional()));
		a = a.union(keyw("WORD"));
		return a;
	}
	
	Automaton property() {
		return s1.concatenate(keyw("NOT")
				.concatenate(s1)
				.concatenate(keyw("NULL"))
				.union(keyw("UNIQUE"))
				.union(keyw("PRIMARY")
						.concatenate(s1)
						.concatenate(keyw("KEY"))));
	}
	
	Automaton atom() {    
		return name.union(string).union(intnum).union(realnum).union(operator("?"));
	}
	
	Automaton where() {
		return s1.concatenate(keyw("WHERE"))
		.concatenate(comp())
		.concatenate(s1
				.concatenate(keyw("AND"))
				.concatenate(comp()).repeat());
	}
	
	Automaton comp() {
		return s1.concatenate(atom()).concatenate(s0).concatenate(comparison()).concatenate(s0).concatenate(atom());
	}
	
	Automaton order() {
		return s1.concatenate(keyw("ORDER")).concatenate(s1).concatenate(keyw("BY")).concatenate(s1).concatenate(commastar(name));
	}
	
	Automaton comparison() {
		return operator("<").union(operator(">"))
		.union(operator(">="))
		.union(operator("<="))
		.union(operator("<>"))
		.union(operator("="));
	}
	
	Automaton upd() {
		return name.concatenate(s0).concatenate(operator("=")).concatenate(s0).concatenate(atom());
	}
	
	SqlAutomaton() {
		name = new RegExp("[A-Za-z][A-Za-z0-9_.]*").toAutomaton();
		string = new RegExp(any_in_string ? "'.*'" : "'[^'\n]*'").toAutomaton();
		intnum = new RegExp("-?[0-9]+").toAutomaton();
		realnum = new RegExp("-?([0-9]+([.][0-9]*)?)|([.][0-9]+)(e[-+]?[0-9]+)?").toAutomaton();
	}
	
	public static void main(String[] args) throws IOException {
		Automaton s0 = Automaton.makeString(" ").repeat();
		boolean dot = false;
		String filename = null;
		for (int i = 0 ; i < args.length ; i++)
			if (args[i].equals("-d")) 
				dot = true;
			else if (args[i].equals("-s"))
				any_in_string = true;
			else
				filename = args[i];
		System.err.println("Generating automaton...");
		Automaton.setMinimizeAlways(true);
		Automaton sql = new SqlAutomaton().sql();
		System.err.println("Automaton has "+sql.getNumberOfStates()+" states and "+sql.getNumberOfTransitions()+" transitions.");
		if (filename != null) {
			System.err.println("Serializing automaton to `"+filename+"'...");
			ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(filename));
			out.writeObject(sql);
			out.close();
		}
		if (dot) {
			System.err.println("Outputting automaton as dot graph...");
			System.out.println(sql.toDot());
		}
	}
}
